/*
 * @Author: Wangjun
 * @Date: 2021-08-16 09:07:58
 * @LastEditTime: 2025-05-08 15:55:40
 * @LastEditors: wangjun haodreams@163.com
 * @Description:
 * @FilePath: \golib\js\jstask\task.go
 * hnxr
 */
package taskjs

import (
	"fmt"
	"time"

	"gitee.com/haodreams/golib/easyjs"
	"gitee.com/haodreams/golib/exception"
	"gitee.com/haodreams/golib/logs"
	"gitee.com/haodreams/libs/easy"
	"gitee.com/haodreams/libs/ee"
	"github.com/dop251/goja"
	"github.com/robfig/cron/v3"
)

type CallBack func(vm *goja.Runtime)

var cb CallBack

// GetCallBack 获取全局回调函数
func GetCallBack() CallBack {
	return cb
}

// 设置全局回调函数, 必须在初始化js脚本前使用
func SetCallBack(f CallBack) {
	cb = f
}

// Task 任务
type Task struct {
	Time         string //执行时间
	Path         string //路径
	Desc         string //描述
	UsedTime     string //执行任务耗时
	LastCallTime int64  //最后一次调用的开始时间
	ModiTime     int64  //文件修改时间
	Count        int    //执行次数
	Running      bool   //执行状态
	ID           int    //任务ID
	Timeout      int64  //超时时间， 如果超过这个时间还没有执行完，则继续执行
	Msg          string //运行时的提示消息
	// initd        bool   //是否每次运行都重新解析js脚本,TODO 后期改进为自动识别
	ready bool //是否准备就绪 true 准备好了， false 未准备就绪
	err   error
	vm    *goja.Runtime //js的运行环境
	setup goja.Callable //初始化函数
	run   goja.Callable //主循环函数
}

// Tasks 任务组
type Tasks struct {
	Time  string
	ID    int
	tasks []*Task
}

func (m *Task) SetReady() {
	if m.err != nil {
		return
	}
	m.ready = true
	if m.setup != nil {
		_, err := m.setup(goja.Undefined())
		if err != nil {
			logs.Error(err)
			m.err = err
		}
	}
	m.Msg = ""
}

// Cancel 取消任务
func (m *Task) Cancel() {
	m.vm = nil
}

// Run 运行任务
func (m *Tasks) Run() {
	for _, task := range m.tasks {
		if task != nil {
			task.Run()
		}
	}
}

func (m *Task) Setup() {
	if m.setup != nil {
		_, err := m.setup(nil)
		if err != nil {
			logs.Warn(m.Path, m.Desc, err)
			m.Msg = err.Error()
			return
		}
	}
}

// SyncTask 同步
type SyncTask struct {
	taskMap map[string]*Tasks
	array   []*Task
	cron    *cron.Cron
}

// GetTasks 获取全部的任务
func (m *SyncTask) GetTasks() []*Task {
	return m.array
}

// AsyncTask 异步
type AsyncTask struct {
	Tasks
	cron *cron.Cron
}

// GetTasks 获取全部的任务
func (m *AsyncTask) GetTasks() []*Task {
	return m.tasks
}

func (m *Task) Run() {
	if !m.ready {
		logs.Info(m.Path, m.Desc, "等待任务就绪...")
		return
	}
	m.work()
	m.Count++
}

// Run 定时执行
func (m *Task) work() {
	now := time.Now()
	if m.Running {
		timeout := easy.IF(m.Timeout == 0, 1800, m.Timeout)
		if now.Unix()-timeout < m.LastCallTime {
			logs.Warn(m.Path, m.Desc, "服务尚未完成, 本次放弃执行")
			return
		}
	}
	m.LastCallTime = now.Unix()
	m.Running = true
	defer func() {
		exception.CatchException()
		m.Running = false
		m.UsedTime = time.Since(now).String()
	}()

	fi, err := GetJs(m.Path, now.Unix())
	if err != nil {
		m.Msg = err.Error()
		m.err = err
		return
	}

	if fi.ModiTime != m.ModiTime {
		m.ModiTime = fi.ModiTime
		vm, setup, run, err := LoadJs(fi.Name, string(fi.Data))
		if err != nil {
			m.err = err
			m.Msg = err.Error()
			return
		}
		if setup != nil {
			_, err = setup(nil)
			if err != nil {
				m.err = err
				logs.Warn(m.Path, m.Desc, err)
				m.Msg = err.Error()
				return
			}
		}

		m.run = run
		m.vm = vm
		m.err = nil
	}
	if m.err != nil {
		return
	}
	if m.vm != nil && m.run != nil {
		data, err := m.run(nil)
		if err != nil {
			logs.Warn(m.Path, m.Desc, err)
			m.Msg = err.Error()
			return
		}
		if data == goja.Undefined() {
			return
		}
		m.Msg = data.String()
	}
}

func LoadJsString(name string, text string) (vm *goja.Runtime, err error) {
	vm = goja.New()
	if cb != nil {
		cb(vm)
	}
	_, err = easyjs.RunScript(vm, name, text)
	if err != nil {
		return nil, ee.New(err).Print(logs.CbWarn)
	}

	return
}

func LoadJs(name string, text string) (vm *goja.Runtime, setup, run goja.Callable, err error) {
	vm, err = LoadJsString(name, text)
	if err != nil {
		err = ee.New(err).Print(logs.CbWarn)
		return
	}
	var ok bool
	setup, _ = goja.AssertFunction(vm.Get("setup"))
	run, ok = goja.AssertFunction(vm.Get("run"))
	if !ok {
		err = ee.NewError(fmt.Sprintf("run() not a function, name: %s", name)).Print(logs.CbWarn)
		return
	}
	return vm, setup, run, err
}
